﻿// -----------------------------------------------------------------------
// <copyright file="ICollectionGenerator.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Generators
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using FluentStatement;
    using FluentStatement.Utilities;

    /// <summary>
    /// 
    /// </summary>
    public static class ICollectionGenerator
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Descriptor BuildSerializer(Serializer serializer, Type parametersType, Type type, Type helper)
        {
            bool isUniversal = true;

            Type decoratedTypeWithHelper = helper.IsGenericTypeDefinition ? helper.MakeGenericType(type) : helper;
            Type decoratedType = type;
            Type realType = Serializer.UndecorateType(type);

            Type[] temp = decoratedType.GetInterfaces(typeof(ICollection<>));

            if (temp.Length != 1)
            {
                throw new NotSupportedException();
            }

            Type decoratedInterfaceType = temp[0];

            temp = realType.GetInterfaces(typeof(ICollection<>));

            if (temp.Length != 1)
            {
                throw new NotSupportedException();
            }

            Type realInterfaceType = temp[0];

            InterfaceMapping mapping = realType.IsInterface ? default(InterfaceMapping) : realType.GetInterfaceMap(realInterfaceType);

            Type decoratedSubItemType = decoratedInterfaceType.GetGenericArguments()[0];
            Type realSubItemType = realInterfaceType.GetGenericArguments()[0];

            ParameterExpression item = Expression.Parameter(realType);
            ParameterExpression parameters = parametersType != null ? Expression.Parameter(parametersType) : null;
            ParameterExpression subItem = Expression.Variable(realSubItemType);

            Descriptor subItemDescriptor = Serializer.GetSerializer(serializer, realSubItemType, decoratedSubItemType, helper);
            isUniversal &= subItemDescriptor.IsUniversal;

            PropertyInfo count = (from p in realType.GetVisibleProperties()
                                  where p.Name == "Count"
                                  let get = p.GetGetMethod(false)
                                  where get != null && !get.IsStatic && p.GetIndexParameters().Length == 0
                                  select p).FirstOrDefault();

            MemberExpression count2;

            if (count != null)
            {
                count2 = Expression.Property(item, count);
            }
            else
            {
                MethodInfo count3 = mapping.TargetMethods[Array.FindIndex(mapping.InterfaceMethods, p => p.Name == "get_Count")];
                count2 = Expression.Property(item, count3);
            }

            ConstructorInfo constructor = decoratedTypeWithHelper.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
            var helper2 = (ICollectionHelper)constructor.Invoke(null);
            isUniversal &= helper2.IsUniversal;

            LambdaExpression disposeExpression;
            LambdaExpression deserializeExpression;
            LambdaExpression serializeExpression;
            LambdaExpression sizeExpression;

            /***/
            {
                var block = new List<Expression>();

                if (typeof(IDisposable).IsAssignableFrom(realType))
                {
                    LambdaExpression realTypeDispose = Serializer.GetDisposeExpression(realType);
                    block.Add(Serializer.MakeFromLambda(realTypeDispose, false, item));
                }

                if (subItemDescriptor.DeserializationHandling.HasFlag(DeserializationHandling.RequireDispose))
                {
                    block.Add(ExpressionEx.ForEach(item, subItemDescriptor.DeserializeExpression, Expression.Label()));
                }

                disposeExpression = block.Count == 0 ? null : Serializer.MakeLambda(Expression.Block(block), false, item);
            }

            /***/
            {
                MethodInfo[] methods = realType.GetVisibleMethods();

                MethodInfo clear = (from p in methods
                                    where p.Name == "Clear" && !p.IsStatic && !p.IsGenericMethod && p.ReturnType == typeof(void) && p.GetParameters().Length == 0
                                    select p).FirstOrDefault();

                if (clear == null)
                {
                    clear = mapping.TargetMethods[Array.FindIndex(mapping.InterfaceMethods, p => p.Name == "Clear")];
                }

                MethodInfo add = (from p in methods
                                  where p.Name == "Add" && !p.IsStatic && !p.IsGenericMethod && p.ReturnType == typeof(void)
                                  let pars = p.GetParameters()
                                  where pars.Length == 1 && pars[0].ParameterType == realSubItemType
                                  select p).FirstOrDefault();

                if (add == null)
                {
                    add = mapping.TargetMethods[Array.FindIndex(mapping.InterfaceMethods, p => p.Name == "Add")];
                }

                var block = new List<Expression>();

                ParameterExpression size = Expression.Variable(typeof(int));

                if (subItemDescriptor.DeserializationHandling.HasFlag(DeserializationHandling.RequireDispose))
                {
                    block.Add(ExpressionEx.ForEach(item, subItemDescriptor.DeserializeExpression, Expression.Label()));
                }

                block.Add(Expression.Call(item, clear));

                block.Add(Expression.Assign(size, helper2.DeserializeLength(Serializer.ReaderExpression, count2) ?? count2));

                Expression subItemDeserialize = Expression.Call(item, add, Serializer.MakeFromLambda(subItemDescriptor.DeserializeExpression, subItemDescriptor.IsUniversal, Serializer.ReaderExpression, subItem, parameters));

                if (!(subItemDescriptor.DefaultExpression is DefaultExpression))
                {
                    subItemDeserialize = Expression.Block(
                        Expression.Assign(subItem, subItemDescriptor.DefaultExpression),
                        subItemDeserialize);
                }

                ForExpression @for = ExpressionEx.For(Expression.PostDecrementAssign(size), Expression.GreaterThan(size, Serializer.Zero), subItemDeserialize, Expression.Label());

                block.Add(@for);
                block.Add(item);

                BlockExpression expression = Expression.Block(new[] { size, subItem }, block);
                deserializeExpression = Serializer.MakeLambda(expression, isUniversal, Serializer.ReaderExpression, item, parameters);
            }

            /***/
            {
                var block = new List<Expression>();

                Expression sizeSerialize = helper2.SerializeLength(Serializer.WriterExpression, count2);

                if (sizeSerialize != null)
                {
                    block.Add(sizeSerialize);
                }

                Expression subItemSerialize = Serializer.MakeFromLambda(subItemDescriptor.SerializeExpression, subItemDescriptor.IsUniversal, Serializer.WriterExpression, subItem, parameters);
                Expression forEach = ExpressionEx.ForEach(item, Expression.Lambda(subItemSerialize, subItem), Expression.Label());

                block.Add(forEach);

                Expression block2 = block.Count == 1 ? block[0] : Expression.Block(block);
                serializeExpression = Serializer.MakeLambda(block2, isUniversal, Serializer.WriterExpression, item, parameters);
            }

            /***/
            {
                Expression baseSize = helper2.SizeLength(count2);

                if (subItemDescriptor.SizeExpression.Body is ConstantExpression)
                {
                    Expression size = Expression.Multiply(subItemDescriptor.SizeExpression.Body, count2);

                    if (baseSize != null)
                    {
                        size = Expression.Add(baseSize, size);
                    }

                    sizeExpression = Serializer.MakeLambda(size, isUniversal, item, parameters);
                }
                else
                {
                    var block = new List<Expression>();

                    ParameterExpression size = Expression.Variable(typeof(int));

                    if (baseSize != null)
                    {
                        block.Add(Expression.Assign(size, baseSize));
                    }
                    else
                    {
                        block.Add(Expression.Assign(size, Serializer.Zero));
                    }

                    Expression subItemSize = Serializer.MakeFromLambda(subItemDescriptor.SizeExpression, subItemDescriptor.IsUniversal, subItem, parameters);
                    ForEachExpression forEach = ExpressionEx.ForEach(item, Expression.Lambda(Expression.AddAssign(size, subItemSize), subItem), Expression.Label());

                    block.Add(forEach);
                    block.Add(size);

                    BlockExpression expression = Expression.Block(new[] { size }, block);
                    sizeExpression = Serializer.MakeLambda(expression, isUniversal, item, parameters);
                }
            }

            var descriptor = new Descriptor(decoratedTypeWithHelper, isUniversal, deserializeExpression, DeserializationHandling.None, serializeExpression, sizeExpression, disposeExpression);
            return descriptor;
        }
    }
}